#include "vcl_stl.h"

#include <algorithm>

#include "bkbase.h"
#include "h_base.h"
#include "h_xml.h"
using namespace h_base;

#include "simbat.h"
using namespace simbat;

static char *SimbatBase;
static char ErrMsg[8192 + 1];

struct HJobInfo
{
   int PartQty;
   bool PartUsed;
   bool Used;

   HJobInfo()
      : PartQty(0), PartUsed(false), Used(false)
   {}
};

static HString SyslogDir;
static HSyslog Syslog;
static HString ShrDir;
static HString JobNameDelimiter = ":";
static map<string, HJobInfo> JobMap;

static void LoadFromCfg(HXmlTree &Cfg)
{
   SyslogDir = EnvEvaluate(Cfg.GetValue("SYSLOG_DIR", "$SIMBAT_BASE/etc"));
   Syslog.Setup(SyslogDir, Cfg.GetValueUpper("SYSLOG_LEVEL", "INFO"));
   ShrDir = EnvEvaluate(Cfg.GetValue("SHR_DIR", "$SIMBAT_BASE/shr"));
   JobNameDelimiter = Cfg.GetValue("JOB_NAME_DELIMITER", ":");
}

static bool LoadCfg(HString &ErrStr)
{
   char FileName[500 + 1];
   HXmlTree Cfg;

   {
      HFileLockRead Lock("$SIMBAT_BASE/tmp/config.lck");
      snprintf(FileName, 500, "%s/etc/config.xml", SimbatBase);
      if ( !Cfg.LoadFromFile(FileName) )
      {
         ErrStr = Cfg.GetErrorString();
         return false;
      }
   }

   LoadFromCfg(Cfg);
   return true;
}

bool Comp(const HJobEvent &lhs, const HJobEvent &rhs)
{
   if ( strcmp(lhs.Time, rhs.Time) < 0 )
   {
      return true;
   }

   if ( strcmp(lhs.Time, rhs.Time) > 0 )
   {
      return false;
   }

   if ( strcmp(lhs.JobName, rhs.JobName) < 0 )
   {
      return true;
   }

   if ( strcmp(lhs.JobName, rhs.JobName) > 0 )
   {
      return false;
   }

   if ( lhs.PartIdx < rhs.PartIdx )
   {
      return true;
   }

   if ( lhs.PartIdx > rhs.PartIdx )
   {
      return false;
   }

   if ( lhs.Status == jsRunning && rhs.Status != jsRunning )
   {
      return true;
   }

   if ( lhs.Status != jsRunning && rhs.Status == jsRunning )
   {
      return false;
   }

   if ( lhs.Status == jsFail && rhs.Status == jsSkipped )
   {
      return true;
   }

   if ( lhs.Status == jsSkipped && rhs.Status == jsFail )
   {
      return false;
   }

   return false;
}

bool LoadJobMap(const char *FileName)
{
   HXmlTree Xml;

   if ( !Xml.LoadFromFile(FileName, false) )
   {
      BkPrintError("Load %s fail, %s", FileName, Xml.GetErrorString().c_str());
      return false;
   }

   vector<HXmlTree*> GroupVec;

   JobMap.clear();
   Xml.GetChildren("GROUP", GroupVec);

   for ( int i = 0; i < GroupVec.size(); i++ )
   {
      HXmlTree *GroupXml = GroupVec[i];
      string GroupName = GroupXml->GetPropertyUpper("NAME");
      vector<HXmlTree*> JobVec;
      GroupXml->GetChildren("JOB", JobVec);
      if ( JobVec.empty() )
      {
         string JobName = GroupName + JobNameDelimiter + "DUMMY";
         JobMap[JobName] = HJobInfo();
      }
      for ( int j = 0; j < JobVec.size(); j++ )
      {
         HXmlTree *JobXml = JobVec[j];
         string JobName = GroupName + JobNameDelimiter + JobXml->GetPropertyUpper("NAME");
         HJobType Type = StringToJobType(JobXml->GetPropertyUpper("TYPE"));
         if ( Type != jtJob && Type != jtConnector && Type != jtEventWait && Type != jtEventFire )
         {
            continue;
         }
         HJobInfo Job;
         string PartitionFile = JobXml->GetProperty("PARTITION_FILE");
         if ( PartitionFile != "" && strchr(PartitionFile.c_str(), '/') == NULL )
         {  
            PartitionFile = ShrDir + "/part/" + PartitionFile;
         }  
         if ( PartitionFile != "" )
         {
            FILE *f = fopen(EnvEvaluate(PartitionFile).c_str(), "rt");
            if ( f == NULL )
            {
               BkPrintError("Open %s fail, errno = %d", PartitionFile.c_str(), errno);
               return false;
            }
            set<HString> PartSet;
            for ( ; ; )
            {
               char Line[500 + 1];
               char PartId[80 + 1];
               if ( ReadLine(f, Line, 500) == -1 )
               {
                  break;
               }
               BkRightTrim(Line);
               if ( Line[0] == '#' || Line[0] == '\0' )
               {
                  continue;
               }
               ReadWord(Line, PartId, " ", 80);
               PartSet.insert(PartId);
            }
            fclose(f);
            Job.PartQty = PartSet.size();
         }
         JobMap[JobName] = Job;
      }
   }

   HString DesFileName = ShrDir + "/status/current.sbt";

   if ( !Xml.SaveToFile(DesFileName.c_str()) )
   {
      BkPrintError("Save current.sbt fail, %s", Xml.GetErrorString().c_str());
      return false;
   }

   return true;
}

bool MakeStatus(const char *FileName)
{
   FILE *f = fopen(FileName, "rt");

   if ( f == NULL )
   {
      BkPrintError("Can not open %s, errno = %d", FileName, errno);
      return false;
   }

   int LineNo = 0;
   vector<HJobEvent> EventVec;

   for ( int i = 0; i < 3; i++ )
   {
      char Line[2048 + 1];
      if ( ReadLine(f, Line, 2048) == -1 )
      {
         break;
      }
      LineNo++;
   }

   for ( ; ; )
   {
      char Line[2048 + 1];
      if ( ReadLine(f, Line, 2048) == -1 )
      {
         break;
      }
      if ( memcmp(Line, "Batch end time", 14) == 0 )
      {
         break;
      }
      LineNo++;
      vector<string> StrVec;
      SplitString(Line, '|', StrVec, true);
      if ( StrVec.size() < 6 )
      {
         continue;
      }
      if ( strchr(StrVec[0].c_str(), '(') != NULL )
      {
         continue;
      }
      if ( StrVec[4] != "SUCCESS" && StrVec[4] != "FAIL" && StrVec[4] != "SKIPPED" )
      {
         continue;
      }
      vector<string> NameStrVec;
      SplitString(StrVec[0].c_str(), JobNameDelimiter[0], NameStrVec, true);
      if ( NameStrVec.size() < 2 )
      {
         continue;
      }
      string JobName = NameStrVec[0] + JobNameDelimiter + NameStrVec[1];
      map<string, HJobInfo>::iterator JobIter = JobMap.find(JobName);
      if ( JobIter == JobMap.end() )
      {
         continue;
      }
      HJobInfo &Job = JobIter->second;
      Job.Used = true;
      HJobEvent Event;
      BkStrNCpy(Event.JobName, JobName.c_str(), sizeof(Event.JobName) - 1);
      const char *w = StrVec[1].c_str();
      if ( NameStrVec.size() == 3 )
      {
         Event.PartIdx = StrToInt(NameStrVec[2].c_str()) - 1;
         if ( Event.PartIdx >= Job.PartQty )
         {
            continue;
         }
         Job.PartUsed = true;
      }
      else
      {
         if ( Job.PartUsed )
         {
            continue;
         }
      }
      Event.ServerId = StrToInt(StrVec[5].c_str()) - 1;
      if ( StrVec[4] != "SKIPPED" )
      {
         memcpy(Event.Time, w, 4);
         memcpy(Event.Time + 4, w + 4 + 1, 2);
         memcpy(Event.Time + 4 + 2, w + 4 + 1 + 2 + 1, 2);
         memcpy(Event.Time + 4 + 2 + 2, w + 11, 2);
         memcpy(Event.Time + 4 + 2 + 2 + 2, w + 11 + 2 + 1, 2);
         memcpy(Event.Time + 4 + 2 + 2 + 2 + 2, w + 11 + 2 + 1 + 2 + 1, 2);
         Event.Status = jsRunning;
         EventVec.push_back(Event);
      }
      w = StrVec[2].c_str();
      memcpy(Event.Time, w, 4);
      memcpy(Event.Time + 4, w + 4 + 1, 2);
      memcpy(Event.Time + 4 + 2, w + 4 + 1 + 2 + 1, 2);
      memcpy(Event.Time + 4 + 2 + 2, w + 11, 2);
      memcpy(Event.Time + 4 + 2 + 2 + 2, w + 11 + 2 + 1, 2);
      memcpy(Event.Time + 4 + 2 + 2 + 2 + 2, w + 11 + 2 + 1 + 2 + 1, 2);
      if ( StrVec[4] == "SUCCESS" )
      {
         Event.Status = jsSuccess;
      }
      else if ( StrVec[4] == "FAIL" )
      {
         Event.Status = jsFail;
      }
      else
      {
         Event.Status = jsSkipped;
      }
      EventVec.push_back(Event);
   }

   fclose(f);

   if ( EventVec.empty() )
   {
      BkPrintError("EventVec is empty");
      return false;
   }

   for ( map<string, HJobInfo>::iterator Iter = JobMap.begin();
         Iter != JobMap.end(); Iter++ )
   {
      if ( !Iter->second.Used )
      {
         BkPrintError("Job '%s' has no status record", Iter->first.c_str());
      }
      Iter->second.PartUsed = false;
   }

   HBatch Batch;
   HString DesFileName = ShrDir + "/status/job_events";

   Batch.Status = bsStopped;
   f = fopen(DesFileName.c_str(), "wb");

   if ( f == NULL )
   {
      BkPrintError("Can not create %s, errno = %d", DesFileName.c_str(), errno);
      return false;
   }

   sort(EventVec.begin(), EventVec.end(), Comp);

   for ( int i = 0; i < EventVec.size(); i++ )
   {
      const HJobEvent &Event = EventVec[i];
      if ( Event.PartIdx >= 0 )
      {
         map<string, HJobInfo>::iterator Iter = JobMap.find(Event.JobName);
         if ( Iter == JobMap.end() )
         {
            continue;
         }
         if ( !Iter->second.PartUsed )
         {
            HJobEvent Event2;
            BkStrNCpy(Event2.JobName, Event.JobName, sizeof(Event2.JobName) - 1);
            BkStrNCpy(Event2.Time, Event.Time, sizeof(Event2.Time) - 1);
            Event2.NumOfPart = Iter->second.PartQty;
            fwrite(&Event2, sizeof(HJobEvent), 1, f);
            Iter->second.PartUsed = true;
         }
      }
      fwrite(&Event, sizeof(HJobEvent), 1, f);
      if ( i == 0 )
      {
         BkStrNCpy(Batch.StartTime, Event.Time, sizeof(Batch.StartTime) - 1);
      }
      if ( i == EventVec.size() - 1 )
      {
         BkStrNCpy(Batch.EndTime, Event.Time, sizeof(Batch.EndTime) - 1);
      }
   }

   for ( map<string, HJobInfo>::iterator Iter = JobMap.begin();
         Iter != JobMap.end(); Iter++ )
   {
      if ( !Iter->second.Used )
      {
         HJobEvent Event;
         BkStrNCpy(Event.JobName, Iter->first.c_str(), sizeof(Event.JobName) - 1);
         BkStrNCpy(Event.Time, Batch.EndTime, sizeof(Event.Time) - 1);
         Event.Status = jsRunning;
         fwrite(&Event, sizeof(HJobEvent), 1, f);
         Event.Status = jsSuccess;
         fwrite(&Event, sizeof(HJobEvent), 1, f);
      }
   }

   fclose(f);
   DesFileName = ShrDir + "/status/batch_status";
   f = fopen(DesFileName.c_str(), "wb");

   if ( f == NULL )
   {
      BkPrintError("Can not create %s, errno = %d", DesFileName.c_str(), errno);
      return false;
   }

   fwrite(&Batch, sizeof(HBatch), 1, f);
   fclose(f);
   return true;
}

void PrintUsage()
{
   fprintf(stderr, "Usage : [PATH]/simbat_mksts sbt_file_name summary_file_name\n");
}

int main(int argc, char *argv[])
{
   if ( strchr(argv[0], '/') != NULL )
   {
      SetBaseByPath(argv[0]);
   }

   if ( argc != 3 )
   {
      PrintUsage();
      return 8;
   }

   SimbatBase = getenv("SIMBAT_BASE");

   if ( SimbatBase == NULL )
   {
      fprintf(stderr, "$SIMBAT_BASE not defined\n");
      return 8;
   }

   HString ErrStr;

   if ( !LoadCfg(ErrStr) )
   {
      BkPrintError("Load config.xml fail, %s", ErrStr.c_str());
      return 8;
   }

   if ( !LoadJobMap(argv[1]) )
   {
      return 8;
   }

   if ( !MakeStatus(argv[2]) )
   {
      return 8;
   }

   return 0;
}
